#include "esp_camera.h"
#include <WiFi.h>
#include <esp_http_server.h>
#include "soc/soc.h"           
#include "soc/rtc_cntl_reg.h"  

const char* ssid = "свой роутер";
const char* password = "свой пароль";

#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>КОШАЧИЙ ДОЗОР MAX</title>
    <style>
        body { font-family: sans-serif; background: #1a1a1a; color: #fff; text-align: center; margin: 0; padding: 10px; }
        h2 { margin: 5px 0; font-size: 20px; color: #ff4444; }
        .container { max-width: 500px; margin: 0 auto; background: #2a2a2a; padding: 15px; border-radius: 8px; }
        img { width: 100%; max-width: 400px; background: #000; border-radius: 5px; }
        .controls { margin-top: 15px; background: #333; padding: 10px; border-radius: 5px; text-align: left; }
        label { display: block; margin: 8px 0 4px; font-size: 14px; }
        input[type="range"] { width: 100%; }
        .status { font-weight: bold; color: #44ff44; margin-top: 10px; font-size: 14px; }
        canvas { display: none; }
    </style>
</head>
<body>
<div class="container">
    <h2>КОШАЧИЙ ДОЗОР [MAX]</h2>
    <img id="stream" src="/stream" alt="Загрузка видеопотока...">
    <div class="status" id="statusLine">Анализ кадра: Старт...</div>
    <div class="controls">
        <label>Чувствительность (MAX = влево):</label>
        <input type="range" id="motionThreshold" min="5" max="50" value="10">
        <label>Порог площади движения (MAX = влево):</label>
        <input type="range" id="detectThreshold" min="1" max="20" value="2" step="0.5">
        <label style="margin-top:15px; color:#ffcc00;">
            <input type="checkbox" id="enableSave" checked> Авто-запись фото в телефон
        </label>
    </div>
</div>
<canvas id="currentCanvas" width="320" height="240"></canvas>
<canvas id="prevCanvas" width="320" height="240"></canvas>
<script>
    const img = document.getElementById('stream');
    const currCanvas = document.getElementById('currentCanvas');
    const prevCanvas = document.getElementById('prevCanvas');
    const currCtx = currCanvas.getContext('2d');
    const prevCtx = prevCanvas.getContext('2d');
    const statusLine = document.getElementById('statusLine');
    const motionSlider = document.getElementById('motionThreshold');
    const detectSlider = document.getElementById('detectThreshold');
    const enableSave = document.getElementById('enableSave');

    let firstFrame = true;
    let lastSaveTime = 0;
    const saveCooldown = 4000;

    setInterval(() => {
        if (!img.complete || img.naturalWidth === 0) return;
        try {
            currCtx.drawImage(img, 0, 0, 320, 240);
            if (firstFrame) {
                prevCtx.drawImage(img, 0, 0, 320, 240);
                firstFrame = false;
                return;
            }
            const currData = currCtx.getImageData(0, 0, 320, 240).data;
            const prevData = prevCtx.getImageData(0, 0, 320, 240).data;
            let triggerPixels = 0;
            const totalPixels = 320 * 240;
            const motionThresh = parseInt(motionSlider.value);
            const detectThresh = parseFloat(detectSlider.value);

            for (let i = 0; i < currData.length; i += 16) {
                const rDiff = Math.abs(currData[i] - prevData[i]);
                const gDiff = Math.abs(currData[i+1] - prevData[i+1]);
                const bDiff = Math.abs(currData[i+2] - prevData[i+2]);
                if ((rDiff + gDiff + bDiff) / 3 > motionThresh) { triggerPixels++; }
            }
            const changedPercentage = (triggerPixels / (totalPixels / 4)) * 100;
            if (changedPercentage > detectThresh) {
                statusLine.innerText = `ТРЕВОГА! Движение: ${changedPercentage.toFixed(1)}%`;
                statusLine.style.color = '#ff4444';
                const now = Date.now();
                if (enableSave.checked && (now - lastSaveTime > saveCooldown)) {
                    lastSaveTime = now;
                    const link = document.createElement('a');
                    link.download = `CATCH_${now}.jpg`;
                    link.href = currCanvas.toDataURL('image/jpeg', 0.8);
                    link.click();
                }
            } else {
                statusLine.innerText = `Всё спокойно (${changedPercentage.toFixed(1)}%)`;
                statusLine.style.color = '#44ff44';
            }
            prevCtx.drawImage(currCanvas, 0, 0);
        } catch (e) {}
    }, 150);
</script>
</body>
</html>
)rawliteral";

void startCameraServer();

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); 
  Serial.begin(115200);
  Serial.setDebugOutput(true);

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  
  config.xclk_freq_hz = 20000000;         
  config.pixel_format = PIXFORMAT_GRAYSCALE; 
  config.frame_size = FRAMESIZE_QVGA;       
  config.jpeg_quality = 15;
  config.fb_count = 1;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) { while(true) { delay(1000); } }

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { delay(500); }
  
  startCameraServer();
  Serial.print("\nURL: http://");
  Serial.println(WiFi.localIP());
}

void loop() { delay(10000); }

void startCameraServer(){
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;
  
  httpd_uri_t index_uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = [](httpd_req_t *req) -> esp_err_t {
      httpd_resp_set_type(req, "text/html");
      return httpd_resp_send(req, index_html, -1); 
    },
    .user_ctx  = NULL
  };

  httpd_uri_t stream_uri = {
    .uri       = "/stream",
    .method    = HTTP_GET,
    .handler   = [](httpd_req_t *req) -> esp_err_t {
      camera_fb_t * fb = NULL;
      esp_err_t res = ESP_OK;
      size_t _jpg_buf_len = 0;
      uint8_t * _jpg_buf = NULL;
      char part_buf[64]; // ИСПРАВЛЕНО: Теперь это честный рабочий массив байт

      res = httpd_resp_set_type(req, "multipart/x-mixed-replace;boundary=123456789000000000000987654321");
      if(res != ESP_OK) return res;

      while(true){
        fb = esp_camera_fb_get();
        if (!fb) { res = ESP_FAIL; } 
        else {
          bool jpeg_converted = fmt2jpg(fb->buf, fb->len, fb->width, fb->height, fb->format, 80, &_jpg_buf, &_jpg_buf_len);
          if(!jpeg_converted) res = ESP_FAIL;
        }
        if(res == ESP_OK){
          size_t hlen = snprintf(part_buf, 64, "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n", _jpg_buf_len);
          res = httpd_resp_send_chunk(req, part_buf, hlen);
        }
        if(res == ESP_OK) res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
        if(res == ESP_OK) res = httpd_resp_send_chunk(req, "\r\n--123456789000000000000987654321\r\n", 36);
        if(_jpg_buf) { free(_jpg_buf); _jpg_buf = NULL; }
        if(fb) { esp_camera_fb_return(fb); }
        if(res != ESP_OK) break;
      }
      return res;
    },
    .user_ctx  = NULL
  };
  
  httpd_handle_t camera_httpd = NULL;
  httpd_start(&camera_httpd, &config);
  httpd_register_uri_handler(camera_httpd, &index_uri);
  httpd_register_uri_handler(camera_httpd, &stream_uri);
}
